package net.tv90.volunteerserveradmin.aop;

import com.alibaba.fastjson2.JSON;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import net.tv90.volunteerserveradmin.annotation.AccessLimit;
import net.tv90.volunteerserveradmin.utils.IpUtils;
import net.tv90.volunteerserveradmin.utils.RedisUtils;
import net.tv90.volunteerserveradmin.utils.entity.Result;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * AccessLimitInterceptor 类用于实现基于注解的访问频率限制（速率限制）。
 * 通过拦截 HTTP 请求，检查请求是否超过了预定义的访问频率限制，并在超过限制时拒绝请求。
 * 该类依赖于 Redis 进行访问计数和限流逻辑的实现。
 */
@Slf4j
@Component
public class AccessLimitInterceptor implements HandlerInterceptor {

    @Resource
    private RedisUtils redisUtils;

    /**
     * 将字符串渲染到客户端。
     * 该方法用于在请求被拒绝时向客户端返回 JSON 格式的错误信息。
     *
     * @param response 渲染对象，用于向客户端发送响应
     * @param string   待渲染的字符串，通常是 JSON 格式的错误信息
     */
    public static void renderString(HttpServletResponse response, String string) {
        try {
            response.setStatus(200);
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            PrintWriter writer = response.getWriter();
            writer.print(string);
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 在请求处理之前进行拦截，执行访问频率限制逻辑。
     *
     * @param request  当前 HTTP 请求
     * @param response 当前 HTTP 响应
     * @param handler  处理器对象，通常是一个控制器方法
     * @return 如果返回 true，表示请求可以继续被处理；如果返回 false，表示请求被拦截，终止后续处理
     */
    @Override
    public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
        boolean result = true;

        // 检查处理器是否为 HandlerMethod 实例（即控制器方法）
        if (handler instanceof HandlerMethod handlerMethod) {
            // 获取控制器方法上的 @AccessLimit 注解
            AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);

            // 如果方法上存在 @AccessLimit 注解，则执行限流逻辑
            if (accessLimit != null) {
                int seconds = accessLimit.seconds(); // 获取限流时间窗口（秒）
                int maxCount = accessLimit.maxCount(); // 获取最大允许请求次数
                String msg = accessLimit.msg(); // 获取限流超时后的提示信息

                String ip = IpUtils.getIpAddress(request); // 获取请求的客户端 IP 地址
                String method = request.getMethod(); // 获取 HTTP 请求方法（GET、POST 等）
                String requestUri = request.getRequestURI(); // 获取请求的 URI

                // 构建 Redis 键，用于存储该 IP 在指定时间窗口内对该 URI 的访问次数
                String redisKey = "accessLimit::" + ip + ":" + method + ":" + requestUri;

                try {
                    Long count = redisUtils.incr(redisKey, 1L); // 将 Redis 键对应的值加 1，返回当前计数

                    // 如果是第一次访问，则设置键的过期时间
                    if (Objects.nonNull(count) && count == 1) {
                        redisUtils.setExpire(redisKey, seconds, TimeUnit.SECONDS);
                    }
                    // 如果访问次数超过最大限制，则拒绝请求
                    else if (count > maxCount) {
                        // 构建失败的结果对象，包含错误提示信息
                        String responseBody = JSON.toJSONString(Result.failed(msg));
                        // 将失败结果渲染到客户端
                        renderString(response, responseBody);
                        // 记录警告日志
                        log.warn(redisKey + " 请求次数超过每 " + seconds + " 秒 " + maxCount + " 次");
                        // 拦截请求，不再继续处理
                        result = false;
                    }
                } catch (RedisConnectionFailureException e) {
                    // 处理 Redis 连接失败的情况
                    log.error("Redis 错误: " + e.getMessage());
                    // 拦截请求，防止因 Redis 问题导致服务异常
                    result = false;
                }
            }
        }

        return result; // 返回是否允许请求继续处理
    }
}
